﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using UtilZ.Dotnet.Ex.Communication.Local.LMQ;

namespace UtilZ.Dotnet.Ex.DataStruct
{
    /// <summary>
    /// 集合变更通知数据源
    /// </summary>
    /// <typeparam name="T">集合元素类型</typeparam>
    /// <typeparam name="TKey">集合元素字典Key类型</typeparam>
    public class ChangedNotifyDataSources<T, TKey> : System.Collections.Specialized.INotifyCollectionChanged, IChangedNotifyDataSourcesCommit
    {
        /// <summary>
        /// 当集合更改时发生
        /// </summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        /// <summary>
        /// 集合变更通知主题
        /// </summary>
        private readonly string _changedNotifyTopic;


        private readonly T _all;
        /// <summary>
        /// 表示全部的项
        /// </summary>
        public T All
        {
            get { return _all; }
        }


        private readonly T _none;
        /// <summary>
        /// 表示未知或空的项
        /// </summary>
        public T None
        {
            get { return _none; }
        }


        private readonly List<T> _list = new List<T>();
        /// <summary>
        /// 仅有效数据列表
        /// </summary>
        public List<T> List
        {
            get { return _list; }
        }



        private readonly List<T> _allList = new List<T>();
        /// <summary>
        /// 包含全部的项的数据列表
        /// </summary>
        public List<T> AllList
        {
            get { return _allList; }
        }

        private readonly List<T> _noneList = new List<T>();
        /// <summary>
        /// 包含未知或空的项的数据列表
        /// </summary>
        public List<T> NoneList
        {
            get { return _noneList; }
        }

        /// <summary>
        /// 字典集合
        /// </summary>
        private readonly Dictionary<TKey, T> _dic = new Dictionary<TKey, T>();

        /// <summary>
        /// 生成字典Key的委托
        /// </summary>
        private readonly Func<T, TKey> _generateKey;

        /// <summary>
        /// 字典Key集合
        /// </summary>
        public Dictionary<TKey, T>.KeyCollection Keys
        {
            get { return this._dic.Keys; }
        }



        /// <summary>
        /// 初始化构造函数
        /// </summary>
        /// <param name="changedNotifyTopic">集合变更通知主题</param>
        /// <param name="all">表示全部的项</param>
        /// <param name="none">表示未知或空的项</param>
        /// <param name="generateKey">生成字典Key的委托</param>
        public ChangedNotifyDataSources(string changedNotifyTopic, T all, T none, Func<T, TKey> generateKey)
        {
            this._changedNotifyTopic = changedNotifyTopic;
            if (!string.IsNullOrEmpty(changedNotifyTopic))
            {
                LMQConfigManager.AddLMQConfig(new LMQConfig(changedNotifyTopic) { SyncPublish = true });
            }

            this._all = all;
            this._none = none;
            this._generateKey = generateKey;

            this.Init();
        }


        private void Init()
        {
            if (this._all != null)
            {
                this._allList.Add(this._all);
                this.AddToDicionary(this._all);
            }

            if (this._none != null)
            {
                this._noneList.Add(this._none);
                this.AddToDicionary(this._none);
            }
        }



        #region Dicionary
        private void AddToDicionary(T item)
        {
            if (this._generateKey == null)
            {
                return;
            }

            TKey key = this._generateKey(item);
            this.PrimitiveAddToDicionary(key, item);
        }

        private void PrimitiveAddToDicionary(TKey key, T item)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            this._dic[key] = item;
        }




        private void RemoveFromDicionary(T item)
        {
            if (this._generateKey == null)
            {
                return;
            }

            TKey key = this._generateKey(item);
            this.RemoveFromDicionary(key);
        }

        private void RemoveFromDicionary(TKey key)
        {
            if (key == null)
            {
                return;
            }

            this._dic.Remove(key);
        }


        /// <summary>
        /// 是否包含指定Key
        /// </summary>
        /// <param name="key">指定Key</param>
        /// <returns>true if the System.Collections.Generic.Dictionary`2 contains an element withthe specified key; otherwise, false</returns>
        public bool ContainsKey(TKey key)
        {
            return this._dic.ContainsKey(key);
        }

        /// <summary>
        /// Gets the value associated with the specified key
        /// </summary>
        /// <param name="key">The key of the value to get</param>
        /// <param name="value">When this method returns, contains the value associated with the specified key,
        /// if the key is found; otherwise, the default value for the type of the value parameter.
        /// This parameter is passed uninitialized</param>
        /// <returns>true if the System.Collections.Generic.Dictionary`2 contains an element with the specified key; otherwise, false</returns>
        public bool TryGetValue(TKey key, out T value)
        {
            return this._dic.TryGetValue(key, out value);
        }
        #endregion



        #region Commit
        private bool _autoCommitDataChangedNotify = true;
        /// <summary>
        /// 设置为手动提交变更通知
        /// </summary>
        public void ManualCommit()
        {
            this._autoCommitDataChangedNotify = false;
        }

        /// <summary>
        /// 设置为自动提交变更通知
        /// </summary>
        public void AutoCommit()
        {
            this._autoCommitDataChangedNotify = true;
        }

        /// <summary>
        /// 数据变更通知,包括集合改变,集合项中的属性值改变
        /// </summary>
        public void CommitDataChangedNotify()
        {
            this.PrimitiveCommitDataChangedNotify(null);
        }

        /// <summary>
        /// 数据变更通知,包括集合改变,集合项中的属性值改变
        /// </summary>
        private void AutoCommitDataChangedNotify(ChangedNotifyDataSourcesArgs args)
        {
            if (this._autoCommitDataChangedNotify)
            {
                this.PrimitiveCommitDataChangedNotify(args);
            }
        }

        private void PrimitiveCommitDataChangedNotify(ChangedNotifyDataSourcesArgs args)
        {
            this.CollectionChanged?.Invoke(this, args);

            if (!string.IsNullOrEmpty(this._changedNotifyTopic))
            {
                LMQCore.Publish(this._changedNotifyTopic, args);
            }
        }
        #endregion



        #region Clear
        /// <summary>
        /// 清空集合
        /// </summary>
        public void Clear()
        {
            this._list.Clear();
            this._noneList.Clear();
            this._allList.Clear();
            this._dic.Clear();
            this.Init();
            this.AutoCommitDataChangedNotify(new ChangedNotifyDataSourcesArgs(this));
        }
        #endregion


        #region Add
        /// <summary>
        /// Adds the elements of the specified collection to the end of the Collections.
        /// </summary>
        /// <param name="collection">The collection whose elements should be added to the end of the System.Collections.Generic.List`1.
        /// The collection itself cannot be null, but it can contain elements that are null,
        /// if type T is a reference type</param>
        public void Add(IEnumerable<T> collection)
        {
            if (collection == null)
            {
                return;
            }

            this._list.AddRange(collection);
            this._noneList.AddRange(collection);
            this._allList.AddRange(collection);
            var args = new ChangedNotifyDataSourcesArgs(this, NotifyCollectionChangedAction.Add, collection);
            foreach (var item in collection)
            {
                this.AddToDicionary(item);
                args.NewItems.Add(item);
            }
            this.AutoCommitDataChangedNotify(args);
        }

        /// <summary>
        /// Adds an object to the end of the Collections
        /// </summary>
        /// <param name="item">The object to be added to the end of the System.Collections.Generic.List`1. The
        /// value can be null for reference types</param>
        public void Add(T item)
        {
            this._list.Add(item);
            this._noneList.Add(item);
            this._allList.Add(item);
            this.AddToDicionary(item);

            var args = new ChangedNotifyDataSourcesArgs(this, NotifyCollectionChangedAction.Add, item);
            this.AutoCommitDataChangedNotify(args);
        }

        /// <summary>
        /// Adds an object to the end of the Collections
        /// </summary>
        /// <param name="key">Key</param>
        /// <param name="item">The object to be added to the end of the System.Collections.Generic.List`1. The
        /// value can be null for reference types</param>
        public void Add(TKey key, T item)
        {
            this._list.Add(item);
            this._noneList.Add(item);
            this._allList.Add(item);
            this.PrimitiveAddToDicionary(key, item);

            var args = new ChangedNotifyDataSourcesArgs(this, NotifyCollectionChangedAction.Add, item);
            args.NewItems.Add(item);
            this.AutoCommitDataChangedNotify(args);
        }
        #endregion


        #region Remove
        /// <summary>
        /// Removes the first occurrence of a specific object from the Collections
        /// </summary>
        /// <param name="collection">移除集合中包含的项</param>
        public void Remove(IEnumerable<T> collection)
        {
            if (collection == null)
            {
                return;
            }

            T[] removeArr = this._list.Where(t => { return collection.Contains(t); }).ToArray();
            this._list.RemoveAll(t => collection.Contains(t));
            this._noneList.RemoveAll(t => collection.Contains(t));
            this._allList.RemoveAll(t => collection.Contains(t));
            var args = new ChangedNotifyDataSourcesArgs(this, NotifyCollectionChangedAction.Remove, removeArr);

            foreach (var item in removeArr)
            {
                this.RemoveFromDicionary(item);
                args.OldItems.Add(item);
            }
            this.AutoCommitDataChangedNotify(args);
        }

        /// <summary>
        /// Removes the first occurrence of a specific object from the Collections
        /// </summary>
        /// <param name="item">The object to remove from the System.Collections.Generic.List`1. The value can
        /// be null for reference types</param>
        public void Remove(T item)
        {
            this._list.Remove(item);
            this._noneList.Remove(item);
            this._allList.Remove(item);
            this.RemoveFromDicionary(item);
            var args = new ChangedNotifyDataSourcesArgs(this, NotifyCollectionChangedAction.Remove, item);
            args.OldItems.Add(item);
            this.AutoCommitDataChangedNotify(args);
        }

        /// <summary>
        /// 移除指定Key的项
        /// </summary>
        /// <param name="key">Key</param>
        public void Remove(TKey key)
        {
            if (key == null)
            {
                return;
            }

            T item;
            if (!this._dic.TryGetValue(key, out item))
            {
                return;
            }

            this._list.Remove(item);
            this._noneList.Remove(item);
            this._allList.Remove(item);
            this.RemoveFromDicionary(key);

            var args = new ChangedNotifyDataSourcesArgs(this, NotifyCollectionChangedAction.Remove, item);
            args.OldItems.Add(item);
            this.AutoCommitDataChangedNotify(args);
        }

        /// <summary>
        /// 移除满足条件的项
        /// </summary>
        /// <param name="match">匹配条件</param>
        /// <returns>移除掉的项数</returns>
        public int RemoveAll(Predicate<T> match)
        {


            List<T> removeList = null;
            foreach (var kv in this._dic.ToArray())
            {
                if (object.ReferenceEquals(kv.Value, this._all) ||
                    object.ReferenceEquals(kv.Value, this._none))
                {
                    continue;
                }

                if (match(kv.Value))
                {
                    this._list.Remove(kv.Value);
                    this._noneList.Remove(kv.Value);
                    this._allList.Remove(kv.Value);
                    this.RemoveFromDicionary(kv.Key);

                    if (removeList == null)
                    {
                        removeList = new List<T>();
                    }

                    removeList.Add(kv.Value);
                }
            }

            if (removeList != null)
            {
                var args = new ChangedNotifyDataSourcesArgs(this, NotifyCollectionChangedAction.Remove, removeList);
                this.AutoCommitDataChangedNotify(args);
                return removeList.Count;
            }

            return 0;
        }
        #endregion

        #region Replace
        /// <summary>
        /// 将旧项替换为新项
        /// </summary>
        /// <param name="oldItem">旧项</param>
        /// <param name="newItem">新项</param>
        public void Replace(T oldItem, T newItem)
        {
            int index = this._list.IndexOf(oldItem);
            if (index < 0)
            {
                return;
            }

            this._list[index] = newItem;

            index = this._allList.IndexOf(oldItem);
            this._allList[index] = newItem;

            index = this._noneList.IndexOf(oldItem);
            this._noneList[index] = newItem;

            TKey key = this._generateKey(oldItem);
            this._dic[key] = newItem;

            var args = new ChangedNotifyDataSourcesArgs(this, newItem, oldItem);
            args.NewItems.Add(newItem);
            args.OldItems.Add(oldItem);
            this.AutoCommitDataChangedNotify(args);
        }
        #endregion



        /// <summary>
        /// Returns the first element of a sequence, or a default value if the sequence contains no elements
        /// </summary>
        /// <param name="firstType">第一项集合类型</param>
        /// <returns>default(TSource) if source is empty; otherwise, the first element in source.</returns>
        public T FirstOrDefault(FirstType firstType)
        {
            List<T> sources;
            switch (firstType)
            {
                case FirstType.AllList:
                    sources = this._allList;
                    break;
                case FirstType.NoneList:
                    sources = this._noneList;
                    break;
                case FirstType.List:
                default:
                    sources = this._list;
                    break;
            }

            if (sources == null || sources.Count == 0)
            {
                return default(T);
            }

            return sources.FirstOrDefault();
        }
    }

    /// <summary>
    /// 集合变更通知数据源变化参数类
    /// </summary>
    public class ChangedNotifyDataSourcesArgs : NotifyCollectionChangedEventArgs
    {
        ///// <summary>
        ///// 描述引起了 System.Collections.Specialized.INotifyCollectionChanged.CollectionChanged事件的操作
        ///// </summary>
        //public NotifyCollectionChangedAction Action { get; private set; }

        ///// <summary>
        ///// 获取更改中涉及的新项的列表
        ///// </summary>
        //public IList NewItems { get; }

        ///// <summary>
        ///// 获取受 System.Collections.Specialized.NotifyCollectionChangedAction.Replace、Remove或 Move 操作影响的各项的列表
        ///// </summary>
        //public IList OldItems { get; }

        /// <summary>
        /// 数据源
        /// </summary>
        public object Sources { get; private set; }



        /// <summary>
        /// 构造函数初始化(Reset)
        /// </summary>
        /// <param name="sources">数据源</param>
        public ChangedNotifyDataSourcesArgs(object sources)
            : base(NotifyCollectionChangedAction.Reset)
        {
            this.Sources = sources;
        }

        /// <summary>
        /// 构造函数初始化(Reset、Add、Remove)
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="action"></param>
        /// <param name="changedItem"></param>
        public ChangedNotifyDataSourcesArgs(object sources, NotifyCollectionChangedAction action, object changedItem)
            : base(action, changedItem)
        {
            this.Sources = sources;
        }

        /// <summary>
        /// 构造函数初始化(Reset、Add、Remove)
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="action"></param>
        /// <param name="changedItems"></param>
        public ChangedNotifyDataSourcesArgs(object sources, NotifyCollectionChangedAction action, IList changedItems)
            : base(action, changedItems)
        {
            this.Sources = sources;
        }

        /// <summary>
        /// 构造函数初始化(Reset、Add、Remove)
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="action"></param>
        /// <param name="changedItem"></param>
        /// <param name="index"></param>
        public ChangedNotifyDataSourcesArgs(object sources, NotifyCollectionChangedAction action, object changedItem, int index)
            : base(action, changedItem, index)
        {
            this.Sources = sources;
        }



        /// <summary>
        /// 构造函数初始化(Replace)
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="newItem"></param>
        /// <param name="oldItem"></param>
        public ChangedNotifyDataSourcesArgs(object sources, object newItem, object oldItem)
            : base(NotifyCollectionChangedAction.Replace, newItem, oldItem)
        {
            this.Sources = sources;
        }
    }

    /// <summary>
    /// 第一项类型
    /// </summary>
    public enum FirstType
    {
        /// <summary>
        /// 所有列表中的第一项
        /// </summary>
        AllList,

        /// <summary>
        /// 未知列表中的第一项
        /// </summary>
        NoneList,

        /// <summary>
        /// 仅数据列表中的第一项
        /// </summary>
        List
    }

    /// <summary>
    /// 集合变更通知数据源提交接口
    /// </summary>
    public interface IChangedNotifyDataSourcesCommit
    {
        /// <summary>
        /// 设置为手动提交变更通知
        /// </summary>
        void ManualCommit();

        /// <summary>
        /// 设置为自动提交变更通知
        /// </summary>
        void AutoCommit();

        /// <summary>
        /// 数据变更通知,包括集合改变,集合项中的属性值改变
        /// </summary>
        void CommitDataChangedNotify();
    }
}
